using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Text;
using System.Xml.Serialization;
using System.Timers;

namespace DeliciousAPI
{
    public class RequestService
    {
        private static string ApiRoot = new DeliciousAPI.Properties.Settings().ApiLocation;
        private const string _POSTS_ALL = "posts/all?";
        private const string _POSTS_RECENT = "posts/recent?";
        private const string _POSTS_DATES = "posts/dates?";
        private const string _POSTS_ADD = "posts/add?";
        private const string _POSTS_LAST_UPDATE = "posts/update";

        private static readonly string _FILE_CACHE_PATH = Path.Combine(Environment.ExpandEnvironmentVariables("%APPDATA%"), @"DeliciousToolbarCache\");
        private static readonly string _POST_CACHE_PATH = Path.Combine(_FILE_CACHE_PATH, "PostCache.xml");
        
        private bool Throttled = false;
        private Timer ThrottleTimer;

        private static int ServerErrorCounter = 0;
        private Timer ServerErrorTimer;
        
        private string Username;
        private string Password;

        private bool fileCache;
        private TimeSpan cacheDuration;

        public RequestService(string username, string password, bool fileCache, TimeSpan cacheDuration)
        {
            this.Username = username;
            this.Password = password;
            this.fileCache = fileCache;
            this.cacheDuration = cacheDuration;

            ThrottleTimer = new Timer(5000);
            ThrottleTimer.AutoReset = false;
            ThrottleTimer.Elapsed += new ElapsedEventHandler(ThrottleTimer_Elapsed);

            ServerErrorTimer = new Timer(60000);
            ServerErrorTimer.AutoReset = false;
            ServerErrorTimer.Elapsed += new ElapsedEventHandler(ServerErrorTimer_Elapsed);
            
        }

        void ServerErrorTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            ServerErrorCounter = 0;
        }

        void ThrottleTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Throttled = false;
        }
        
        public bool GetAllPosts(DateTime newerThanDate, out PostCollection Posts)
        {
            bool returnCache = false;
            Posts = new PostCollection();
            
            if (this.fileCache)
            {
                try
                {
                    DateTime LastUpdate = File.GetLastWriteTime(_POST_CACHE_PATH);
                    if (DateTime.Now <= LastUpdate.Add(this.cacheDuration))
                        returnCache = true;
                }
                catch (IOException)
                {
                    returnCache = false;
                }
            }

            if (this.fileCache && returnCache)
            {
                try
                {
                    using (FileStream CacheFile = File.OpenRead(_POST_CACHE_PATH))
                    {
                        Posts = (PostCollection)Deserialize(typeof(PostCollection), CacheFile);
                        return true;
                    }
                }
                catch (Exception)
                {
                    returnCache = false;
                }
            }

            if (!Throttled && ServerErrorCounter == 0 && !returnCache)
            {
                if (true || GetLastUpdate().Timestamp > newerThanDate)
                {
                    WebRequest Com = GetAuthenticatedRequest(_POSTS_ALL);
                    try
                    {
                        HttpWebResponse Res = (HttpWebResponse)Com.GetResponse();

                        using (Stream Response = Res.GetResponseStream())
                        {
                            Posts = (PostCollection)Deserialize(typeof(PostCollection), Response);
                        }

                        if (this.fileCache)
                        {
                            try
                            {
                                this.UpdateCache(Posts, typeof(PostCollection), _POST_CACHE_PATH);
                            }
                            catch (Exception e)
                            { }
                        }
                    }
                    catch (WebException we)
                    {
                        if (we.Response != null)
                            ValidateHttpResponse(((HttpWebResponse)we.Response).StatusCode);
                    }
                    return true;
                }
                else
                {
                    Debug.WriteLine("Skipping post update, current post collection is already up to date", "Delicious Toolbar");
                }
            }
            return false;
        }
        
        public LastUpdate GetLastUpdate()
        {
            WebRequest Com = GetAuthenticatedRequest(_POSTS_LAST_UPDATE);
            LastUpdate LastUpdate = new LastUpdate();
            LastUpdate.Timestamp = DateTime.MinValue;
            if (!Throttled && ServerErrorCounter == 0)
            {
                try
                {
                    HttpWebResponse Res = (HttpWebResponse)Com.GetResponse();

                    using (Stream Response = Res.GetResponseStream())
                    {
                        LastUpdate = (LastUpdate)Deserialize(typeof(LastUpdate), Response);
                    }
                }
                catch (WebException we)
                {
                    if (we.Response != null)
                        ValidateHttpResponse(((HttpWebResponse)we.Response).StatusCode);
                }
                
            }
            return LastUpdate;
        }
        
        public bool AddPost(Post post)
        {
            if (Throttled)
                return false;
            
            StringBuilder Query = new StringBuilder();
            Query.Append("url=" + post.Href);
            Query.Append("&description=" + post.Description);
            if (post.Extended != null && post.Extended.Length > 0)
                Query.Append("&extended=" + post.Extended);
            if (post.Tag != null && post.Tag.Length > 0)
                Query.Append("&tags=" + post.Tag);
            if (post.Time != null && post.Time.Length > 0)
                Query.Append("&dt=" + post.Time);
            
            Query.Append("&replace=no");
            
            HttpWebRequest Req = (HttpWebRequest)GetAuthenticatedRequest(_POSTS_ADD + Query);
            try
            {
                HttpWebResponse Res = (HttpWebResponse)Req.GetResponse();
                using (StreamReader sr = new StreamReader(Res.GetResponseStream()))
                {
                    string Result = sr.ReadToEnd();
                    if (Result.IndexOf("done") > 0)
                        return true;
                }
            }
            catch (WebException we)
            {
                if (we.Response != null)
                    ValidateHttpResponse(((HttpWebResponse)we.Response).StatusCode);
            }

            return false;
        }
        
        
        private WebRequest GetAuthenticatedRequest(string query)
        {
            WebRequest Request = HttpWebRequest.Create(string.Format("{0}{1}", ApiRoot, query));
            Request.PreAuthenticate = true;
            Request.Credentials = new NetworkCredential(Username, Password);

            return Request;
        }
        
        private void ValidateHttpResponse(HttpStatusCode statusCode)
        {
            switch (statusCode)
            {
                case HttpStatusCode.OK:
                    return;
                case HttpStatusCode.Unauthorized:
                    throw new SecurityException();
                case HttpStatusCode.ServiceUnavailable:
                    // we are probably being throttled from to many requests, enable the throttle
                    // and start the timer which will unthrottle the app in 5 seconds.
                    Debug.WriteLine("We have been throttled", "Delicious Toolbar");
                    this.Throttled = true;
                    ThrottleTimer.Start();
                    break;
                case HttpStatusCode.InternalServerError:
                    Debug.WriteLine("Error response from the server", "Delicious Toolbar");
                    ServerErrorCounter++;
                    this.ServerErrorTimer.Start();
                    break;
                default:
                    throw new Exception(string.Format("Http Status code {0} returned from the server", statusCode.ToString()));
            }
        }
        
        private object Deserialize(Type t, Stream stream)
        {
            object o = null;
            XmlSerializer Serializer = new XmlSerializer(t);
#if DEBUG            
            using (StreamReader sr = new StreamReader(stream))
            {
                string Xml = sr.ReadToEnd();
                Debug.WriteLine(Xml, "Delicious Toolbar");
                
                StringReader Reader = new StringReader(Xml);
                o = Serializer.Deserialize(Reader);
            }
#endif 
            if (o == null)
                o = Serializer.Deserialize(stream);
            return o;
        }

        private void UpdateCache(object o, Type t, string cachePath)
        {
            XmlSerializer Serializer = new XmlSerializer(t);

            try
            {
                using (FileStream CacheFile = File.Open(_POST_CACHE_PATH, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
                {
                    Serializer.Serialize(CacheFile, o);
                }
            }
            catch (DirectoryNotFoundException)
            {
                try
                {
                    Directory.CreateDirectory(_FILE_CACHE_PATH);
                    UpdateCache(o, t, cachePath);
                }
                catch (Exception)
                { }
                
            }
        }
    }
}
